﻿using Raven.Client;
using Raven.Client.Document;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using PServiceBus.Core.Runtime;
using PServiceBus.Core.Logger;
using PServiceBus.Core.Interface;
using System.Collections;

namespace PServiceBus.RavenDB
{
    public class Provider<TObject> : IHaveEndpoint, IObjectProvider<TObject> where TObject : class
    {
        private string _indexName = string.Empty;

        private bool _disposed;

        private static IDocumentStore _store;
        public IDocumentStore Store {
            get {
                if (_store == null) {
                    _store = new DocumentStore { Url = Endpoint };
                    _store.Conventions.CustomizeJsonSerializer = serializer => {
                        serializer.TypeNameAssemblyFormat = System.Runtime.Serialization.Formatters.FormatterAssemblyStyle.Simple;
                        serializer.TypeNameHandling = Newtonsoft.Json.TypeNameHandling.All;
                    };
                    _store.Initialize();
                }
                return _store;
            }
        }

        private IDocumentSession _session;
        private IDocumentSession CurrentSession { get { return (_session = Store.OpenSession()) ?? _session; } }

        private void Commit() {
            CurrentSession.SaveChanges();
        }

        #region IObjectProvider<TObject> Members

        public bool Refresh(TObject obj) {
            throw new NotImplementedException();
        }

        public bool Add(IEnumerable<TObject> list) {
            foreach (var obj in list) CurrentSession.Store(obj);
            Commit();
            return true;
        }

        public bool Add(TObject obj) {
            CurrentSession.Store(obj);
            Commit();
            return true;
        }

        public void Clear() {
            CurrentSession.Clear();
            Commit();
        }

        public void Delete() {
            Clear();
        }

        public bool Delete(IEnumerable<TObject> list) {
            foreach (var obj in list) CurrentSession.Delete(obj);
            Commit();
            return true;
        }

        public bool Delete(IEnumerable<Func<TObject, bool>> conditions) {
            foreach (var condition in conditions) {
                var deleteObj = this.FirstOrDefault(obj => obj != null && condition(obj));
                if (deleteObj != null) CurrentSession.Delete(deleteObj);
            }
            Commit();
            return true;
        }

        public bool Delete(Func<TObject, bool> condition) {
            var deleteObj = this.FirstOrDefault(obj => obj != null && condition(obj));
            if (deleteObj != null) CurrentSession.Delete(deleteObj);
            Commit();
            return true;
        }

        public bool Exists(Func<TObject, bool> condition) {
            return Get(condition) != null;
        }

        public TObject Get(Func<TObject, bool> condition) {
            _indexName = _indexName ?? typeof(TObject).FullName.Replace(".", "_");
            return CurrentSession.LuceneQuery<TObject>(_indexName).FirstOrDefault(condition);
        }

        public bool Exists(TObject obj) {
            var id = IDFunc(obj);
            return this.Any(o => IDFunc(o) == id);
        }

        public TObject Get(Guid id) {
            return this.FirstOrDefault(obj => IDFunc(obj) == id);
        }

        public bool Delete(TObject obj) {
            CurrentSession.Delete(obj);
            Commit();
            return true;
        }

        public string Endpoint { get; set; }

        public string EndpointAlias { get; set; }

        public bool AppendTypeNameToEndpoint { get; set; }

        public bool DeleteObjectOnRead { get; set; }

        public Func<TObject, Guid> IDFunc { get; set; }
        #endregion

        #region IEnumerable<TObject> Members

        public IEnumerator<TObject> GetEnumerator() {
            var item = default(TObject);
            var isDirty = false;
            try {
                var results = CurrentSession.LuceneQuery<TObject>()
                    .WaitForNonStaleResults();
                foreach (var entity in results) {
                    item = entity;
                    if (DeleteObjectOnRead) {
                        CurrentSession.Delete<TObject>(entity);
                        isDirty = true;
                    }
                    yield return item;
                }
            } finally {
                if (isDirty) CurrentSession.SaveChanges();
            }
        }

        #endregion

        #region IEnumerable Members

        IEnumerator IEnumerable.GetEnumerator() {
            return GetEnumerator();
        }

        #endregion

        #region IDisposable Members

        public void Dispose() {
            if (_disposed) return;
            CurrentSession.Dispose();
            _session = null;
            _disposed = true;
        }

        #endregion
    }
}
